import * as fs from "fs";
import * as path from "path";
import * as morph from "ts-morph";

//#region 调试方法
function DebugFileCheck(filePath: string) {
    let list: string[] = [
        "ServerInfoManagerComponentSystem",
        "NetInnerComponentSystem",
        "MessageDispatcherComponentSystem",
        "SessionIdleCheckerComponentSystem",
        "AccountCheckOutTimeComponentSystem",
    ];
    let baseName = path.basename(filePath);
    let extName = path.extname(baseName);
    let fileName = baseName.replace(extName, "");
    if (list.length == 0) {
        return false;
    } else {
        for (let i = 0; i < list.length; i++) {
            if (list.indexOf(fileName) != -1) {
                return false;
            }
        }
    }
    return true;
}

function DebugClassCheck(className: string) {
    let list: string[] = [
    ];
    if (list.length == 0) {
        return false;
    } else {
        for (let i = 0; i < list.length; i++) {
            if (list.indexOf(className) != -1) {
                return false;
            }
        }
    }
    return true;
}

function DebugMethodCheck(funcitonName: string) {
    let list: string[] = [
    ];
    if (list.length == 0) {
        return false;
    } else {
        for (let i = 0; i < list.length; i++) {
            if (list.indexOf(funcitonName) != -1) {
                return false;
            }
        }
    }
    return true;
}
//#endregion

//#region 帮助方法
function FormatFilePath(filePath: string) {
    return filePath.replace(/\\/g, "\\");
}
function IsDecoratorFile(filePath: string) {
    let formatedFilePath = FormatFilePath(filePath);
    if (formatedFilePath.startsWith(decoratorFolderPath)) {
        return true;
    }
    return false;
}

function IsSupportFile(filePath: string) {
    let formatedFilePath = FormatFilePath(filePath);
    if (formatedFilePath.startsWith(supportFolderPath)) {
        return true;
    }
    return false;
}

function IsCoreFile(filePath: string) {
    let formatedFilePath = FormatFilePath(filePath);
    if (formatedFilePath.startsWith(coreFolderPath)) {
        return true;
    }
    return false;
}

function IsModelFile(filePath: string) {
    let formatedFilePath = FormatFilePath(filePath);
    if (formatedFilePath.startsWith(modelFolderPath)) {
        return true;
    }
    return false;
}

function IsDontNeedPreLoadFile(filePath: string) {
    if (IsDecoratorFile(filePath) || IsSupportFile(filePath) || IsCoreFile(filePath) || IsModelFile(filePath)) {
        return true;
    }
    return false;
}

function IsDontNeedCheckImportFile(filePath: string) {
    if (IsDecoratorFile(filePath) || IsSupportFile(filePath) || IsCoreFile(filePath)) {
        return true;
    }
    return false;
}

function GetValidImportedFileList(filePath: string) {
    const validFileList: string[] = [];
    const fileList = allCodeFileImportFileListDict[filePath];
    if (fileList) {
        for (let i = 0; i < fileList.length; i++) {
            const needFile = fileList[i];
            if (IsDontNeedCheckImportFile(needFile)) {
                continue;
            }
            if (allModuleFileList.indexOf(needFile) == -1 && allExpandFileList.indexOf(needFile) == -1) {
                continue;
            }
            validFileList.push(needFile);
        }
    }
    return validFileList;
}

function GetNeedPreLoadImportedFileList(filePath: string, mergedFileList: string[]) {
    const needPreLoadFileList: string[] = [];
    const fileList = allCodeFileImportFileListDict[filePath];
    if (fileList) {
        for (let i = 0; i < fileList.length; i++) {
            const needFile = fileList[i];
            if (IsDontNeedCheckImportFile(needFile)) {
                continue;
            }
            if (mergedFileList.indexOf(needFile) == -1 && allModuleFileList.indexOf(needFile) == -1 && allExpandFileList.indexOf(needFile) == -1) {
                continue;
            }
            needPreLoadFileList.push(needFile);
        }
    }
    return needPreLoadFileList;
}

function FileIsCollected(validFile: string, collectedFileList: string[]) {
    if (collectedFileList.indexOf(validFile) == -1) {
        return false;
    }
    return true;
}

function FileListIsCollected(fileList: string[], collectedFileList: string[]) {
    for (let i = 0; i < fileList.length; i++) {
        let file = fileList[i];
        if (!FileIsCollected(file, collectedFileList)) {
            return false;
        }
    }
    return true;
}

function ExpandFileIsCollected(validFile: string, collectedFileList: string[]) {
    const fileList = GetValidImportedFileList(validFile);
    const mFileList = expand2ModuleFileListDict[validFile];
    for (let i = 0; i < fileList.length; i++) {
        const needFile = fileList[i];
        if (mFileList.indexOf(needFile) == -1) {
            const mIndex = allModuleFileList.indexOf(needFile);
            if (mIndex == -1) {
                const tempFileList = expand2ModuleFileListDict[needFile];
                if (!FileIsCollected(needFile, collectedFileList) || !FileListIsCollected(tempFileList, collectedFileList)) {
                    return false;
                }
            } else {
                const tempFileList = module2ExpandFileListDict[needFile];
                if (!FileIsCollected(needFile, collectedFileList) || !FileListIsCollected(tempFileList, collectedFileList)) {
                    return false;
                }
            }
        } else {
            if (!FileIsCollected(needFile, collectedFileList)) {
                return false;
            }
        }
    }
    return true;
}

function Transform2RelativeFilePath(filePath: string) {
    return filePath.replace(realFolderPath, "");
}

function IsBuiltinModuleImportPath(moduleImportPath: string) {
    if (moduleImportPath.indexOf("/") == -1 && moduleImportPath.indexOf("\\") == -1) {
        return true;
    }
    return false;
}

function NeedSkipClass(decoratorList: morph.Decorator[]) {
    let needSkip = false;
    decoratorList.forEach(decorator => {
        const decoratorName = decorator.getName();
        if (needFilterDecoratorList.indexOf(decoratorName) != -1) {
            needSkip = true;
        }
    });
    return needSkip;
}
//#endregion

//#region 遍历所有文件
function CheckFileNeedPreLoad(inputPath: string) {
    const project = new morph.Project({ compilerOptions: { target: morph.ts.ScriptTarget.ES2016 } });
    project.addSourceFileAtPathIfExists(inputPath);

    const morphSourceFile = project.getSourceFileOrThrow(inputPath);

    allCodeFileList.push(inputPath);

    if (!allFileClassMethodListDict[inputPath]) {
        allFileClassMethodListDict[inputPath] = {};
    }

    const classDecls = morphSourceFile.getClasses();
    classDecls.forEach(classDecl => {
        const className = classDecl.getName();

        const decoratorList = classDecl.getDecorators();
        if (NeedSkipClass(decoratorList)) {
            return;
        }

        if (!allFileClassMethodListDict[inputPath][className]) {
            allFileClassMethodListDict[inputPath][className] = [];
        }
        if (!allClassFileMethodListDict[className]) {
            allClassFileMethodListDict[className] = {};
        }
        if (!allClassFileMethodListDict[className][inputPath]) {
            allClassFileMethodListDict[className][inputPath] = [];
        }
        const methodDecls = classDecl.getMethods();
        methodDecls.forEach(methodDecl => {
            const methodName = methodDecl.getName();
            allFileClassMethodListDict[inputPath][className].push(methodName);
            allClassFileMethodListDict[className][inputPath].push(methodName);
        });
    });

    const importDecls = morphSourceFile.getImportDeclarations();
    importDecls?.forEach(importDecl => {
        const moduleImportPath = importDecl.getModuleSpecifierValue();
        if (IsBuiltinModuleImportPath(moduleImportPath)) {
            return;
        }
        const moduleFilePath = path.join(path.dirname(inputPath), moduleImportPath);
        const moduleFile = moduleFilePath + ".ts";
        if (!allCodeFileImportFileListDict[inputPath]) {
            allCodeFileImportFileListDict[inputPath] = [];
        }
        allCodeFileImportFileListDict[inputPath].push(moduleFile);
    });

    // 收集所有 declare module 扩展方法
    const moduleDecls = morphSourceFile.getModules();
    moduleDecls?.forEach(moduleDecl => {
        const moduleStructrue = moduleDecl.getStructure();
        if (!moduleStructrue.hasDeclareKeyword) {
            return;
        }
        if (moduleStructrue.declarationKind != "module") {
            return;
        }
        const moduleImportPath = moduleStructrue.name.replace(/\"/g, "");
        const moduleFilePath = path.join(path.dirname(inputPath), moduleImportPath);
        const moduleFile = moduleFilePath + ".ts";
        if (allModuleFileList.indexOf(moduleFile) == -1) {
            allModuleFileList.push(moduleFile);
        }
        if (allExpandFileList.indexOf(inputPath) == -1) {
            allExpandFileList.push(inputPath);
        }

        if (!originModule2ExpandFileListDict[moduleFile]) {
            originModule2ExpandFileListDict[moduleFile] = [];
        }
        if (originModule2ExpandFileListDict[moduleFile].indexOf(inputPath) == -1) {
            originModule2ExpandFileListDict[moduleFile].push(inputPath);
        } else {
            // console.log(`${inputPath} 已存在`);
        }

        if (!module2ExpandFileListDict[moduleFile]) {
            module2ExpandFileListDict[moduleFile] = [];
        }
        if (module2ExpandFileListDict[moduleFile].indexOf(inputPath) == -1) {
            module2ExpandFileListDict[moduleFile].push(inputPath);
        } else {
            // console.log(`${inputPath} 已存在`);
        }

        if (!originExpand2ModuleFileListDict[inputPath]) {
            originExpand2ModuleFileListDict[inputPath] = [];
        }
        if (originExpand2ModuleFileListDict[inputPath].indexOf(moduleFile) == -1) {
            originExpand2ModuleFileListDict[inputPath].push(moduleFile);
        } else {
            // console.log(`${moduleFile} 已存在`);
        }

        if (!expand2ModuleFileListDict[inputPath]) {
            expand2ModuleFileListDict[inputPath] = [];
        }
        if (expand2ModuleFileListDict[inputPath].indexOf(moduleFile) == -1) {
            expand2ModuleFileListDict[inputPath].push(moduleFile);
        } else {
            // console.log(`${moduleFile} 已存在`);
        }

        const interfaceList = moduleDecl.getInterfaces();
        interfaceList.forEach(interfaceDecl => {
            const className = interfaceDecl.getName();

            if (!allFileClassMethodListDict[inputPath][className]) {
                allFileClassMethodListDict[inputPath][className] = [];
            }
            if (!allClassFileMethodListDict[className]) {
                allClassFileMethodListDict[className] = {};
            }
            if (!allClassFileMethodListDict[className][inputPath]) {
                allClassFileMethodListDict[className][inputPath] = [];
            }
            const methodDecls = interfaceDecl.getMethods();
            methodDecls.forEach(methodDecl => {
                const methodName = methodDecl.getName();
                allFileClassMethodListDict[inputPath][className].push(methodName);
                allClassFileMethodListDict[className][inputPath].push(methodName);
            });
        });
    });

}

async function CollectValidCheck(srcFilePath: string) {
    const ext = path.extname(srcFilePath);
    if (ext != ".ts") {
        return;
    }
    CheckFileNeedPreLoad(srcFilePath);
}

async function TraverseDirectory(dirPath: string) {
    const files = fs.readdirSync(dirPath);
    files.forEach(async (file) => {
        if (needFilterFolderOrFileList.indexOf(file) !== -1) {
            return;
        }
        const filePath = path.join(dirPath, file);
        CollectAllFiles(filePath);
    });
}

function CollectAllFiles(filePath: string) {
    const stats = fs.statSync(filePath);
    if (stats.isDirectory()) {
        TraverseDirectory(filePath);
    } else {
        CollectValidCheck(filePath);
    }
}
//#endregion

//#region 收集所有被扩展类和扩展类
function CollectExpandFileList() {
    const fullPathList: string[] = [];
    const collectedFileList: string[] = [];
    const sortedModuleFileList: string[] = [];
    const allFileList: string[] = [];
    allModuleFileList.forEach((filePath) => {
        if (allFileList.indexOf(filePath) == -1) {
            allFileList.push(filePath);
        }
    });
    allExpandFileList.forEach((filePath) => {
        if (allFileList.indexOf(filePath) == -1) {
            allFileList.push(filePath);
        }
    });
    let index = 0;
    while (allModuleFileList.length != collectedFileList.length) {
        let moduleFile = allModuleFileList[index];

        if (collectedFileList.indexOf(moduleFile) == -1) {
            let isSupported = true;
            const validFileList = GetValidImportedFileList(moduleFile);
            if (validFileList.length != 0) {
                for (let i = 0; i < validFileList.length; i++) {
                    const validFile = validFileList[i];
                    if (FileIsCollected(validFile, collectedFileList)) {
                        continue;
                    }
                    isSupported = false;
                }
            }
            if (isSupported) {
                if (collectedFileList.indexOf(moduleFile) == -1) {
                    collectedFileList.push(moduleFile);
                    sortedModuleFileList.push(moduleFile);
                    // console.log(moduleFile);
                }
            }
        }

        index++;
        if (index >= allModuleFileList.length) {
            index = 0;
        }
    }
    index = 0;
    while (allFileList.length != collectedFileList.length) {
        let moduleFile = sortedModuleFileList[index];

        const expandFileList = originModule2ExpandFileListDict[moduleFile];
        if (expandFileList.length != 0) {
            for (let i = 0; i < expandFileList.length; i++) {
                const expandFile = expandFileList[i];
                if (collectedFileList.indexOf(expandFile) == -1) {
                    if (!ExpandFileIsCollected(expandFile, collectedFileList)) {
                        continue;
                    }
                    if (collectedFileList.indexOf(expandFile) == -1) {
                        collectedFileList.push(expandFile);
                        // console.log(expandFile);
                    }
                }
            }
        }

        index++;
        if (index >= sortedModuleFileList.length) {
            index = 0;
        }
    }
    collectedFileList.forEach((tempFilePath) => {
        fullPathList.push(tempFilePath);
    });
    return fullPathList;
}
//#endregion

//#region 收集被扩展类的使用者
function CollectUsedFileList(needPreLoadFileList: string[]) {
    let usedFullPathList: string[] = [];
    needPreLoadFileList.forEach((filePath) => {
        let importFileList = allCodeFileImportFileListDict[filePath];
        for (let i = 0; i < importFileList.length; i++) {
            let importFilePath = importFileList[i];
            if (needPreLoadFileList.indexOf(importFilePath) != -1) {
                continue;
            }
            let importImportFileList = allCodeFileImportFileListDict[importFilePath];
            if (!importImportFileList) {
                continue;
            }
            if (usedFullPathList.indexOf(importFilePath) == -1) {
                usedFullPathList.push(importFilePath);
            }
        }
    });
    return usedFullPathList;
}
//#endregion

//#region 合并预加载清单
function MergeFileList(expandFileList: string[], usedFileList: string[]) {
    let mergedFileList: string[] = [];
    let preLoadFileList = expandFileList.concat(usedFileList);
    preLoadFileList.forEach(filePath => {
        if (IsDontNeedPreLoadFile(filePath)) {
            return;
        }
        mergedFileList.push(filePath);
    });
    return mergedFileList;
}
//#endregion

//#region 构建调用依赖字典
function BuildCallDependencyDict(mergedFileList: string[]) {
    for (let i = 0; i < mergedFileList.length; i++) {
        let filePath = mergedFileList[i];
        let collectedFileList = GetNeedPreLoadImportedFileList(filePath, mergedFileList);
        allCollectedFileCallImportFileListDict[filePath] = collectedFileList;
    }
}
//#endregion

//#region 整理扩展方法信息
function BuildDirectExpandData() {
    allModuleFileList.forEach((filePath) => {
        let expandFileList = module2ExpandFileListDict[filePath];
        if (expandFileList.length == 1) {
            module2ExpandFileDict[filePath] = expandFileList[0];
            singleExpandModuleFileList.push(filePath);
        }
    });
    allExpandFileList.forEach((filePath) => {
        let moduleFileList = expand2ModuleFileListDict[filePath];
        if (moduleFileList.length == 1) {
            expand2ModuleFileDict[filePath] = moduleFileList[0];
            singleModuleExpandFileList.push(filePath);
        }
    });

    let fileMethodListDict = allClassFileMethodListDict[entityClassName];
    for (let filePath in fileMethodListDict) {
        entityExpandFileList.push(filePath);
        entityMethodListDict[filePath] = fileMethodListDict[filePath];
    }
}
//#endregion

//#region 分组收集预加载清单
function BuildEntityMethodList(collectedFileList: string[]) {
    for (let i = 0; i < entityExpandFileList.length; i++) {
        let filePath = entityExpandFileList[i];
        if (!IsCoreFile(filePath) && collectedFileList.indexOf(filePath) == -1) {
            continue;
        }
        if (entityExpandFileCollectedList.indexOf(filePath) != -1) {
            continue;
        }
        entityExpandFileCollectedList.push(filePath);

        let methodNameList = entityMethodListDict[filePath];
        for (let j = 0; j < methodNameList.length; j++) {
            let methodName = methodNameList[j];
            if (entityMethodList.indexOf(methodName) == -1) {
                entityMethodList.push(methodName);
            } else {
                console.log(`Entity 扩展方法重复 ${filePath} -> ${methodName}`);
            }
        }
    }
}

function IsValidCallImportFilePath(filePath: string, className: string, methodName: string) {
    let methodNameList: string[] = [];
    if (allFileClassMethodListDict[filePath] && allFileClassMethodListDict[filePath][className]) {
        methodNameList = allFileClassMethodListDict[filePath][className];
    }
    if (methodNameList.indexOf(methodName) != -1) {
        return true;
    }
    if (entityMethodList.indexOf(methodName) != -1) {
        return true;
    }
    // console.log(`${filePath} -> ${className} -> ${methodName}`);
    return false;
}

function GetExpandFilePath(className: string, methodName: string) {
    let expandFilePath = "";
    let methodListDict = allClassFileMethodListDict[className];
    for (let filePath in methodListDict) {
        let methodList = methodListDict[filePath];
        if (methodList.indexOf(methodName) != -1) {
            expandFilePath = filePath;
            break;
        }
    }
    return expandFilePath;
}

function CollectCallDependency(inputPath: string, filePathList: string[], mergedFileList: string[]) {
    let collectedFileList: string[] = [];

    const project = new morph.Project({ compilerOptions: { target: morph.ts.ScriptTarget.ES2016 } });
    project.addSourceFileAtPathIfExists(inputPath);

    const morphSourceFile = project.getSourceFileOrThrow(inputPath);

    const classDecls = morphSourceFile.getClasses();
    classDecls?.forEach(classDecl => {
        const methodDecls = classDecl.getMethods();
        methodDecls?.forEach(methodDecl => {
            let nodes = methodDecl.getDescendants();
            nodes.forEach(node => {
                if (node.getFullText() == "") {
                    return;
                }
                if (!node.isKind(morph.ts.SyntaxKind.CallExpression)) {
                    return;
                }
                const expression = node.getExpression();
                if (!expression.isKind(morph.ts.SyntaxKind.PropertyAccessExpression)) {
                    return;
                }
                const methodName = expression.getNameNode()?.getText();
                const expressionNode = expression.getExpression();
                const moduleNode = expressionNode?.getType();
                const moduleSymbol = moduleNode.getSymbol();
                const className = moduleSymbol?.getName();
                const filePath = moduleSymbol?.getValueDeclaration()?.getParent()?.getSourceFile()?.getFilePath();
                if (moduleSymbol) {
                    if (!filePath) {
                        return;
                    }
                    let formatFilePath = filePath.replace(/\//g, "\\");
                    if (filePathList.indexOf(formatFilePath) == -1) {
                        return;
                    }
                    let isValid = IsValidCallImportFilePath(formatFilePath, className, methodName);
                    if (isValid) {
                        return;
                    }
                    let expandFilePath = GetExpandFilePath(className, methodName);
                    collectedFileList.push(expandFilePath);
                } else {
                    // TODO 无法 通过 AST 直接解析出方法调用者的信息，需要辅助其他方式处理
                    console.log(`AST 解析未能生效 ${node.getText()}`);
                }
            });
        });
    });

    return collectedFileList;
}

function TransformImportFileList(filePathList: string[]) {
    let resultFileList: string[] = [];
    for (let i = 0; i < filePathList.length; i++) {
        let filePath = filePathList[i];
        if (allModuleFileList.indexOf(filePath) == -1) {
            resultFileList.push(filePath);
            continue;
        }
        if (singleExpandModuleFileList.indexOf(filePath) != -1) {
            let expandFilePath = module2ExpandFileDict[filePath];
            resultFileList.push(expandFilePath);
            continue;
        }
    }
    return resultFileList;
}

function CheckImportFileIsLoaded(mergedFileList: string[], filePath: string) {
    let needPreLoad = true;
    let needCheckFileList: string[] = [];
    if (IsModelFile(filePath)) {
        needCheckFileList = module2ExpandFileListDict[filePath];
    } else {
        needCheckFileList.push(filePath);
    }
    if (needCheckFileList) {
        for (let i = 0; i < needCheckFileList.length; i++) {
            let tempFilePath = needCheckFileList[i];
            if (mergedFileList.indexOf(tempFilePath) != -1) {
                needPreLoad = false;
                break;
            }
        }
    }
    return needPreLoad;
}

function GetSortedFileListStep4(mergedFileList: string[], sortedFileList: string[], collectedFileList: string[], filteredModule2ExpandFileListDict: Dict<string[]>, filteredCollectedFileCallImportFileListDict: Dict<string[]>) {
    let resultFileList: string[] = [];
    while (collectedFileList.length != 0) {
        for (let i = 0; i < mergedFileList.length; i++) {
            let filePath = mergedFileList[i];
            if (sortedFileList.indexOf(filePath) != -1) {
                continue;
            }
            let isAllCollected = true;
            let callImportFilePathList = filteredCollectedFileCallImportFileListDict[filePath];
            for (let j = 0; j < callImportFilePathList.length; j++) {
                let callImportFilePath = callImportFilePathList[j];
                if (sortedFileList.indexOf(callImportFilePath) == -1 && resultFileList.indexOf(callImportFilePath) == -1) {
                    isAllCollected = false;
                    break;
                }
            }
            if (!isAllCollected) {
                continue;
            }
            resultFileList.push(filePath);
            let index = collectedFileList.indexOf(filePath);
            if (index != -1) {
                collectedFileList.splice(index, 1);
            }
        }
    }
    return resultFileList;
}

function GetSortedFileListStep3(mergedFileList: string[], sortedFileList: string[], collectedFileList: string[], filteredModule2ExpandFileListDict: Dict<string[]>, filteredCollectedFileCallImportFileListDict: Dict<string[]>) {
    let resultFileList: string[] = [];
    for (let i = 0; i < mergedFileList.length; i++) {
        let filePath = mergedFileList[i];
        if (sortedFileList.indexOf(filePath) != -1) {
            continue;
        }
        let needCheckFilePathList: string[] = [];
        let callImportFilePathList = filteredCollectedFileCallImportFileListDict[filePath];
        for (let j = 0; j < callImportFilePathList.length; j++) {
            let callImportFilePath = callImportFilePathList[j];
            if (allModuleFileList.indexOf(callImportFilePath) != -1 && singleExpandModuleFileList.indexOf(callImportFilePath) == -1) {
                needCheckFilePathList.push(callImportFilePath);
            }
        }
        let dependencyFileList: string[] = [];
        if (needCheckFilePathList.length == 0) {
            dependencyFileList = TransformImportFileList(callImportFilePathList);
        } else {
            dependencyFileList = CollectCallDependency(filePath, callImportFilePathList, mergedFileList);
        }
        if (dependencyFileList.length != 0) {
            filteredCollectedFileCallImportFileListDict[filePath] = dependencyFileList;
            continue;
        }
        resultFileList.push(filePath);
        let index = collectedFileList.indexOf(filePath);
        if (index != -1) {
            collectedFileList.splice(index, 1);
        }
    }
    return resultFileList;
}

function GetSortedFileListStep2(mergedFileList: string[], sortedFileList: string[], collectedFileList: string[], filteredModule2ExpandFileListDict: Dict<string[]>, filteredCollectedFileCallImportFileListDict: Dict<string[]>) {
    let resultFileList: string[] = [];
    for (let i = 0; i < mergedFileList.length; i++) {
        let filePath = mergedFileList[i];
        if (sortedFileList.indexOf(filePath) != -1) {
            continue;
        }
        let fileList = allCollectedFileCallImportFileListDict[filePath];
        if (!fileList) {
            continue;
        }
        if (!filteredCollectedFileCallImportFileListDict[filePath]) {
            filteredCollectedFileCallImportFileListDict[filePath] = [];
        }
        let filterdCollectedFileList = filteredCollectedFileCallImportFileListDict[filePath];
        let isAllCollected = true;
        for (let j = 0; j < fileList.length; j++) {
            let importFilePath = fileList[j];
            let expandFileList = originModule2ExpandFileListDict[importFilePath];
            let importFileList = GetNeedPreLoadImportedFileList(importFilePath, mergedFileList);
            if (!expandFileList && !importFileList) {
                if (sortedFileList.indexOf(importFilePath) == -1) {
                    isAllCollected = false;
                }
                continue;
            }
            let isExpandAllCollected = true;
            if (!filteredModule2ExpandFileListDict[importFilePath]) {
                filteredModule2ExpandFileListDict[importFilePath] = [];
            }
            let filterdExpandFileList = filteredModule2ExpandFileListDict[importFilePath];
            if (expandFileList) {
                for (let k = 0; k < expandFileList.length; k++) {
                    let tempFilePath = expandFileList[k];
                    if (tempFilePath == filePath) {
                        continue;
                    }
                    if (tempFilePath == importFilePath) {
                        continue;
                    }
                    if (CheckImportFileIsLoaded(mergedFileList, tempFilePath)) {
                        continue;
                    }
                    if (sortedFileList.indexOf(tempFilePath) != -1) {
                        continue;
                    }
                    if (filterdExpandFileList.indexOf(tempFilePath) == -1) {
                        filterdExpandFileList.push(tempFilePath);
                    }
                    isAllCollected = false;
                    isExpandAllCollected = false;
                }
                if (filterdExpandFileList.length != 0) {
                    filteredModule2ExpandFileListDict[importFilePath] = filterdExpandFileList;
                }
                if (!isExpandAllCollected) {
                    if (filterdCollectedFileList.indexOf(importFilePath) == -1) {
                        filterdCollectedFileList.push(importFilePath);
                    }
                }
            } else if (importFileList) {
                for (let k = 0; k < importFileList.length; k++) {
                    let tempFilePath = importFileList[k];
                    if (tempFilePath == filePath) {
                        continue;
                    }
                    if (tempFilePath == importFilePath) {
                        continue;
                    }
                    if (CheckImportFileIsLoaded(mergedFileList, tempFilePath)) {
                        continue;
                    }
                    if (sortedFileList.indexOf(tempFilePath) != -1) {
                        continue;
                    }
                    if (filterdExpandFileList.indexOf(tempFilePath) == -1) {
                        filterdExpandFileList.push(tempFilePath);
                    }
                    isAllCollected = false;
                    isExpandAllCollected = false;
                }
                if (filterdExpandFileList.length != 0) {
                    filteredModule2ExpandFileListDict[importFilePath] = filterdExpandFileList;
                }
                if (!isExpandAllCollected) {
                    if (filterdCollectedFileList.indexOf(importFilePath) == -1) {
                        filterdCollectedFileList.push(importFilePath);
                    }
                }
            }
        }
        if (filterdCollectedFileList.length != 0) {
            filteredCollectedFileCallImportFileListDict[filePath] = filterdCollectedFileList;
        }
        if (!isAllCollected) {
            continue;
        }
        resultFileList.push(filePath);
        let index = collectedFileList.indexOf(filePath);
        if (index != -1) {
            collectedFileList.splice(index, 1);
        }
    }
    return resultFileList;
}

function GetSortedFileListStep1(mergedFileList: string[], sortedFileList: string[], collectedFileList: string[]) {
    let resultFileList: string[] = [];
    for (let i = 0; i < mergedFileList.length; i++) {
        let filePath = mergedFileList[i];
        if (sortedFileList.indexOf(filePath) != -1) {
            continue;
        }
        let fileList = allCollectedFileCallImportFileListDict[filePath];
        if (fileList && fileList.length != 1) {
            continue;
        }
        let moduleFileList = originExpand2ModuleFileListDict[filePath];
        if (!moduleFileList) {
            continue;
        }
        let moduleFilePath = moduleFileList[0];
        let expandFileList = originModule2ExpandFileListDict[moduleFilePath];
        if (expandFileList.length != 1) {
            continue;
        }
        let expandFilePath = expandFileList[0];
        if (expandFilePath != filePath) {
            continue;
        }
        resultFileList.push(filePath);
        let index = collectedFileList.indexOf(filePath);
        if (index != -1) {
            collectedFileList.splice(index, 1);
        }
    }
    return resultFileList;
}

function CollectFileListByGroup(mergedFileList: string[]) {
    let filteredModule2ExpandFileListDict: Dict<string[]> = {};
    let filteredCollectedFileCallImportFileListDict: Dict<string[]> = {};

    let collectedFileList = mergedFileList.concat();
    let sortedFileList: string[] = [];

    let sortedFileList1 = GetSortedFileListStep1(mergedFileList, sortedFileList, collectedFileList);
    sortedFileList = sortedFileList.concat(sortedFileList1);

    let sortedFileList2 = GetSortedFileListStep2(mergedFileList, sortedFileList, collectedFileList, filteredModule2ExpandFileListDict, filteredCollectedFileCallImportFileListDict);
    sortedFileList = sortedFileList.concat(sortedFileList2);

    BuildEntityMethodList(sortedFileList);

    let sortedFileList3 = GetSortedFileListStep3(mergedFileList, sortedFileList, collectedFileList, filteredModule2ExpandFileListDict, filteredCollectedFileCallImportFileListDict);
    sortedFileList = sortedFileList.concat(sortedFileList3);

    let sortedFileList4 = GetSortedFileListStep4(mergedFileList, sortedFileList, collectedFileList, filteredModule2ExpandFileListDict, filteredCollectedFileCallImportFileListDict);
    sortedFileList = sortedFileList.concat(sortedFileList4);

    return sortedFileList.concat(collectedFileList);
}
//#endregion

//#region 格式化预加载清单
function FormatFileList(mergedFileList: string[]) {
    let totalPathList: string[] = [];
    mergedFileList.forEach((mergedFilePath) => {
        let relativeFilePath = Transform2RelativeFilePath(mergedFilePath);
        if (totalPathList.indexOf(relativeFilePath) == -1) {
            totalPathList.push(relativeFilePath);
        }
    });
    return totalPathList;
}
//#endregion

//#region 生成CodePreLoader清单
function BuildCodePreLoader(inputPath: string, filePathList: string[]) {
    const project = new morph.Project({ compilerOptions: { target: morph.ts.ScriptTarget.ES2016 } });
    project.addSourceFileAtPathIfExists(inputPath);
    // 获取包含当前光标位置的节点。
    const morphSourceFile = project.getSourceFileOrThrow(inputPath);
    const classDecl = morphSourceFile.getClass("CodePreLoader");
    const methodDecl = classDecl.getStaticMethod("GetFilePathList");
    methodDecl.removeBody();
    methodDecl.addStatements(`let list: string[] = []; `);
    filePathList.forEach(filePath => {
        methodDecl.addStatements(`list.push("${filePath.replace(/\\/g, "/")}"); `);
    });
    methodDecl.addStatements(`return list; `);
    const newFileContent = morphSourceFile.getFullText();
    fs.writeFileSync(inputPath, newFileContent);
}
//#endregion

function Main() {
    let startTime = new Date().getTime();
    // 遍历所有文件，收集所有扩展方法关联信息。
    CollectAllFiles(realFolderPath);
    // 收集被扩展类和扩展类
    let expandFileList = CollectExpandFileList();
    // 收集被扩展类的使用者
    let usedFileList = CollectUsedFileList(expandFileList);
    // 合并预加载清单
    let mergedFileList = MergeFileList(expandFileList, usedFileList);
    // 构建调用依赖字典
    BuildCallDependencyDict(mergedFileList);
    // 整理扩展方法信息
    BuildDirectExpandData();
    // 分组收集预加载清单
    let fileListByGroup = CollectFileListByGroup(mergedFileList);
    // 格式化预加载清单
    let formattedFileList = FormatFileList(fileListByGroup);
    // 写入预加载清单
    BuildCodePreLoader(cacheFileFullPath, formattedFileList);

    let endTime = new Date().getTime();
    console.log(`Build Code Time: ${endTime - startTime}ms`);
}

interface Dict<T> {
    [key: string]: T;
}

const allCodeFileList: string[] = [];
const allCodeFileImportFileListDict: Dict<string[]> = {};

const allModuleFileList: string[] = [];
const allExpandFileList: string[] = [];

const originModule2ExpandFileListDict: Dict<string[]> = {};
const originExpand2ModuleFileListDict: Dict<string[]> = {};

const module2ExpandFileListDict: Dict<string[]> = {};
const expand2ModuleFileListDict: Dict<string[]> = {};

const module2ExpandFileDict: Dict<string> = {};
const expand2ModuleFileDict: Dict<string> = {};

const singleExpandModuleFileList: string[] = [];
const singleModuleExpandFileList: string[] = [];

const allCollectedFileCallImportFileListDict: Dict<string[]> = {};

const allFileClassMethodListDict: Dict<Dict<string[]>> = {};

const allClassFileMethodListDict: Dict<Dict<string[]>> = {};

const entityClassName = "Entity";
const entityExpandFileList: string[] = [];
const entityExpandFileCollectedList: string[] = [];
const entityMethodListDict: Dict<string[]> = {};
const entityMethodList: string[] = [];

const srcFolderPath = "Codes/scripts";
const needFilterFolderOrFileList = ["Client", "CodeLoader.ts", "CodePreLoader.ts"];
const rootFolderPath = path.join(__dirname, "../../../");
const realFolderPath = path.join(rootFolderPath, srcFolderPath);

const cacheFileFullPath = path.join(realFolderPath, "Support/Server/Loader/CodePreLoader.ts");

const decoratorFolderPath = path.join(realFolderPath, "Decorator");
const supportFolderPath = path.join(realFolderPath, "Support");
const coreFolderPath = path.join(realFolderPath, "Core");
const modelFolderPath = path.join(realFolderPath, "Codes\\Model");

const needFilterDecoratorList = ["ExpandOf", "ObjectSystemAttribute"];

Main();